package com.njcb.ams.support.security.util;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;

import com.njcb.ams.factory.domain.AppContext;
import com.njcb.ams.portal.SysBaseDefine;
import com.njcb.ams.support.exception.ExceptionUtil;
import com.njcb.ams.support.security.bo.SecurityUser;
import com.njcb.ams.util.AmsAssert;
import com.njcb.ams.util.SysInfoUtils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * 
 * @author LOONG
 *
 */
public class TokenUtil {
	
	/**
	 * @param user
	 * @return
	 */
	public static String createToken(SecurityUser user) {
		return createToken(24*60*60*1000, user, new HashMap<String, Object>());
	}
	
	/**
	 * @param user
	 * @param claims
	 * @return
	 */
	public static String createToken(SecurityUser user, Map<String, Object> claims) {
		return createToken(24*60*60*1000, user, claims);
	}

	/**
	 * 
	 * @param ttlMillis 有效时间，在毫秒区间内有效
	 * @param user 生成token的用户
	 * @param claims 信息载体
	 * @return
	 */
	public static String createToken(long ttlMillis, SecurityUser user, Map<String, Object> claims) {
		// 生成签名的时候使用的秘钥secret,服务端的私钥，不应该流露出去。
		String key = SysInfoUtils.getSysId()+ SysBaseDefine.PLATFORM_VERSION;
		AmsAssert.hasLength(key, 4, "私钥过于简单");
		
		// 指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
		SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

		// 生成JWT的时间
		long nowMillis = System.currentTimeMillis();
		Date now = new Date(nowMillis);

		// 创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
		claims.put("id", user.getId());
		AmsAssert.notNull(user.getUsername(),"用户名不能为空");
		claims.put("username", user.getUsername());
		// 生成签发人
		String subject = user.getUsername();
		Map<String, Object> headMap = new HashMap<>();
		headMap.put("alg", SignatureAlgorithm.HS256.getValue());
        headMap.put("typ", "JWT");
        
		// 下面就是在为payload添加各种标准声明和私有声明了
		// 这里其实就是new一个JwtBuilder，设置jwt的body
		JwtBuilder builder = Jwts.builder().setHeader(headMap)
				// 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
				.setClaims(claims)
				// 设置jti(JWT
				// ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
				.setId(UUID.randomUUID().toString())
				// iat: jwt的签发时间
				.setIssuedAt(now)
				// 代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
				.setSubject(subject)
				// 设置签名使用的签名算法和签名使用的秘钥
				.signWith(signatureAlgorithm, key);
		if (ttlMillis >= 0) {
			long expMillis = nowMillis + ttlMillis;
			Date exp = new Date(expMillis);
			// 设置过期时间
			builder.setExpiration(exp);
		}
		return builder.compact();
	}

	/**
	 * Token的解密
	 * 
	 * @param token 加密后的token
	 * @param user 用户的对象
	 * @return
	 */
	public static Claims parseToken(String token) {
		// 签名秘钥，和生成的签名的秘钥一样
		String key = SysInfoUtils.getSysId()+ SysBaseDefine.PLATFORM_VERSION;
		// 得到DefaultJwtParser
		try {
			Claims claims = Jwts.parser()
					// 设置签名的秘钥
					.setSigningKey(key)
					// 设置需要解析的jwt
					.parseClaimsJws(token).getBody();
			return claims;
		} catch (Exception e) {
			e.printStackTrace();
			ExceptionUtil.throwAppException("token解析错误");
		}
		return null;
	}

	public static void cacheAddToken(String tokenId, Map<String,Object> data){
		CacheManager cacheManager = (CacheManager) AppContext.getBean("cacheManager");
		Cache cache = cacheManager.getCache("TokenCache");
		cache.put(tokenId, data);
	}
	
	@SuppressWarnings("unchecked")
	public static Map<String,Object> cacheGetToken(String tokenId){
		CacheManager cacheManager = (CacheManager) AppContext.getBean("cacheManager");
		Cache cache = cacheManager.getCache("TokenCache");
		ValueWrapper value = cache.get(tokenId);
		if(null == value){
			return null;
		}
		Object getval = value.get();
		return (Map<String, Object>) getval;
	}
	
	public static void main(String[] args) {
		SecurityUser userInfo = new SecurityUser();
		userInfo.setUserName("1");
		userInfo.setPassWord("213123");
		String token = TokenUtil.createToken(userInfo);
		System.out.println(token);
		Claims claims = TokenUtil.parseToken(token);
		System.out.println(claims);
		System.out.println(claims.getId());
		System.out.println(claims.get("password"));
	}

}